﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MacomberMapSystem.Common.Integration;
using System.Drawing.Drawing2D;
using System.Drawing;
using MacomberMapSystem.Common.CIM;

namespace MacomberMapSystem.Common.User_Interfaces.Mapping
{
    /// <summary>
    /// This class holds the graphical representation of a line
    /// </summary>
    public class MM_NetworkMap_Line
    {
        #region Variable declarations
        /// <summary>The line upon which this graphical represenation is based</summary>
        public MM_Line BaseLine;

        /// <summary>Our graphics path for the line</summary>
        public GraphicsPath LinePath;

        /// <summary>A low-resolution rectangle corresponding to the extrema of the line, for rapid hit-testing</summary>
        private Rectangle LowResRectangle;

        /// <summary>Whether the line is visible </summary>
        public bool Visible = false;

        /// <summary>The base CIM element</summary>
        public CIM_Element BaseElement;

        /// <summary>Our collection of unique points</summary>
        public Dictionary<Point, PointD[]> UniquePoints = new Dictionary<Point, PointD[]>();
        #endregion

        #region Initialization
        /// <summary>
        /// Initialize a new graphical representation of a line
        /// </summary>
        /// <param name="BaseLine">The line upon which the graphics are based</param>
        /// <param name="Coordinates">The system coordinates</param>
        /// <param name="BaseElement"></param>
        public MM_NetworkMap_Line(MM_Line BaseLine, CIM_Element BaseElement, MM_NetworkMap_Display Coordinates)
        {
            this.BaseLine = BaseLine;
            this.BaseElement = BaseElement;
            this.LinePath = new GraphicsPath();
            RecomputeLine(Coordinates);
        }
        #endregion

        #region Coordinates handling
        /// <summary>
        /// Recompute a line's positioning
        /// </summary>
        /// <param name="Coordinates"></param>
        public void RecomputeLine(MM_NetworkMap_Display Coordinates)
        {
            Point Stn1 = MM_NetworkMap_Display.LatLngToXY(BaseLine.FromSubstation.LatLong, Coordinates.ZoomLevel);
            Point Stn2 = MM_NetworkMap_Display.LatLngToXY(BaseLine.ToSubstation.LatLong, Coordinates.ZoomLevel);
            int MinX = Math.Min(Stn1.X, Stn2.X);
            int MinY = Math.Min(Stn1.Y, Stn2.Y);
            int MaxX = Math.Max(Stn1.X, Stn2.X);
            int MaxY = Math.Max(Stn1.Y, Stn2.Y);

            this.LinePath.Dispose();
            this.LinePath = new GraphicsPath();

            Dictionary<Point, List<PointD>> UniquePoints = new Dictionary<Point, List<PointD>>();            
            Point LastPoint = MM_NetworkMap_Display.LatLngToXY(BaseLine.Coordinates[0], Coordinates.ZoomLevel);
            UniquePoints.Add(new Point(LastPoint.X/6, LastPoint.Y/6), new List<PointD>(5));
            for (int a = 1; a < BaseLine.Coordinates.Length; a++)
            {
                Point ThisPoint = MM_NetworkMap_Display.LatLngToXY(BaseLine.Coordinates[a], Coordinates.ZoomLevel);
                Point LowResPoint = new Point(ThisPoint.X/6, ThisPoint.Y/6);
                List<PointD> UniquePt;
                if (!UniquePoints.TryGetValue(LowResPoint, out UniquePt))
                    UniquePoints.Add(LowResPoint, UniquePt = new List<PointD>(5));
                UniquePt.Add(BaseLine.Coordinates[a]);
                
                
                if (ThisPoint.X != LastPoint.X || ThisPoint.Y != LastPoint.Y)                
                    this.LinePath.AddLine(LastPoint, ThisPoint);

                LastPoint = ThisPoint;
                MinX = Math.Min(MinX, LastPoint.X);
                MinY = Math.Min(MinY, LastPoint.Y);
                MaxX = Math.Max(MaxX, LastPoint.X);
                MaxY = Math.Max(MaxY, LastPoint.Y);
            }

            this.UniquePoints.Clear();
            foreach (KeyValuePair<Point, List<PointD>> kvp in UniquePoints)
                this.UniquePoints.Add(kvp.Key, kvp.Value.ToArray());

            LowResRectangle = Rectangle.FromLTRB(MinX, MinY, MaxX, MaxY);
        }

        /// <summary>
        /// Test to see whether a line is hit, based on its pixel coordinates
        /// </summary>
        /// <param name="CoordinateXY"></param>
        /// <returns></returns>
        public bool HitTest(Point CoordinateXY)
        {
            if (!LowResRectangle.Contains(CoordinateXY))
                return false;
            else
                return LinePath.IsOutlineVisible(CoordinateXY, new Pen(Color.Brown, 6f));
        }

        /// <summary>
        /// Test to see whether a line is hit, and if so, provide the index point
        /// </summary>
        /// <param name="CoordinateXY"></param>
        /// <returns></returns>
        public PointD HitTestPoke(Point CoordinateXY)
        {
            if (!LowResRectangle.Contains(CoordinateXY))
                return PointD.Empty;
            Point LowResPoint = new Point(CoordinateXY.X / 6, CoordinateXY.Y / 6);
            PointD[] FoundPoints;
            if (UniquePoints.TryGetValue(LowResPoint, out FoundPoints))
                return FoundPoints[0];
            return PointD.Empty;
        }
        #endregion

        /// <summary>
        /// Draw our line
        /// </summary>
        /// <param name="g"></param>
        /// <param name="BaseLine"></param>        
        /// <param name="DisplayOptions"></param>
        public void DrawLine(Graphics g, MM_Line BaseLine, MM_NetworkMap_Display DisplayOptions)
        {
            if (LinePath.PointCount < 2)
                return;
            g.DrawPath(BaseLine.KVLevel.Energized.ForePen, LinePath);
            PointD[] FoundPoints;
            if (DisplayOptions.ZoomLevel >= DisplayOptions.LinePokePointLevel)
                foreach (PointF pt in LinePath.PathPoints)
                {
                    if (!UniquePoints.TryGetValue(new Point((int)pt.X / 6, (int)pt.Y / 6), out FoundPoints))
                        Console.WriteLine("Can't find point " + pt.ToString() + " for line " + BaseLine.Name);
                    else if (FoundPoints.Length == 1)
                    {
                        g.FillEllipse(Brushes.White, pt.X -3f, pt.Y - 3f, 6f, 6f);
                        g.DrawEllipse(Pens.Black, pt.X - 3f, pt.Y - 3f, 6f, 6f);
                    }
                    else
                    {
                        g.FillRectangle(Brushes.Red, pt.X - 2f, pt.Y - 2f, 4, 4);
                        //g.DrawRectangle(Pens.Black, pt.X - 3f, pt.Y - 3f, 6, 6);
                        
                    }   
                }            
        }
        /// <summary>
        /// Report an easy-to-read name for our line
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return BaseLine.ToString();
        }
    }
}
